﻿using System;
using System.Runtime.Serialization;
using CI.Interfaces;

namespace CI.Organism
{
    /// <summary>
    /// Use it or lose it class 
    /// </summary>
    [DataContract]
    public class UseItOrLoseIt : LearningRule, ILearningRule
    {
        /// <summary>
        /// ctor that sets up the learning to adjust the weight on the synaptic connections
        /// </summary>
        /// <param name="gainLR">gain learning rate</param>
        /// <param name="loseLR">lose learning rate</param>
        /// <param name="synapse">synapse which the learning rule belongs to</param>
        /// <param name="guid">guid that will uniquely identify the learning rule</param>
        public UseItOrLoseIt(double gainLR, double loseLR, ISynapse synapse, Guid guid)
        {
            #region Argument Checks
            if (guid == Guid.Empty)
                throw new ArgumentException("guid");
            if (synapse == null)
                throw new ArgumentException("synapse");
            #endregion

            SimEnv.TraceLogger.LogDebug(string.Format("Learning Rule created on synapse {0} with gain rate of {1} and lose rate of {2}", synapse.Guid, gainLR, loseLR));

            // save the values
            _synapse = synapse;
            _synapse.LearningRule = this;
            Guid = guid;
            _gainLearningRate = gainLR;
            _lossLearningRate = loseLR;
        }

        /// <summary>
        /// Notification that the target neuron has or hasnt fired and update
        /// </summary>
        /// <param name="fired">did or didnt it fire?</param>
        public void TargetNeuronFired(bool fired)
        {
            // setup value to adjust by
            double adjustmentValue = 0;
            double oldWeight = _synapse.InputWeight;
            bool initNeg = Math.Sign(_synapse.InitInputWeight) < 0;


            // if it fired adjust
            if (fired)
            {
                adjustmentValue += _gainLearningRate;
            }
            else
            {
                adjustmentValue -= _lossLearningRate;
            }

            // adjust the value relative to whether its a negative or positive value
            if (initNeg)
            {
                adjustmentValue *= -1;
            }

            _synapse.InputWeight = _synapse.InputWeight + adjustmentValue;

            // make sure we cant change the bias
            if ((_synapse.InputWeight > 0 && initNeg)
                || (_synapse.InputWeight < 0 && !initNeg))
            {
                _synapse.InputWeight = 0;
            }


            SimEnv.TraceLogger.LogDebug(string.Format("Input Weight on synapse {0} has been adjusted from {1} to {2} with a adjustment value of {3}", _synapse.Guid, oldWeight, _synapse.InputWeight, adjustmentValue));
        }

        /// <summary>
        /// Take this learning rule and create a learning rule to be put in the new child synapse
        /// Mutate the current learning rule and return a new one, deep class level mutation
        /// </summary>
        /// <param name="mutationRate">mutation rate to be applied</param>
        /// <param name="childSynapse">child synapse it will be going into</param>
        /// <returns>new learning rule for child synapse</returns>
        public ILearningRule Mutate(double mutationRate, ISynapse childSynapse)
        {
            SimEnv.TraceLogger.LogDebug("Mutating UseItorLose");
            // create the new learning rule
            return new UseItOrLoseIt(Mutation.MutateCrawlOrLeap(GainLearningRate, mutationRate), Mutation.MutateCrawlOrLeap(LossLearningRate, mutationRate), childSynapse, Guid);
        }
    }
}